package ch.nostromo.tiffanys.game.game;

import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

import uk.ac.cam.cl.projectcharlie2010.clients.desktop.controller.DesktopController;

import ch.nostromo.tiffanys.game.board.Board;
import ch.nostromo.tiffanys.game.board.BoardException;
import ch.nostromo.tiffanys.game.chess.Chess;
import ch.nostromo.tiffanys.game.chess.ChessException;
import ch.nostromo.tiffanys.game.move.Move;
import ch.nostromo.tiffanys.game.move.MoveException;
import ch.nostromo.tiffanys.game.move.MoveInput;
import ch.nostromo.tiffanys.game.move.MoveTools;
import ch.nostromo.tiffanys.game.pgn.PGN;
import ch.nostromo.tiffanys.lib.ChessTools;

public class Game implements Cloneable {

  private static Logger logger = Logger.getLogger(Game.class.getName());

  public static final int COLOR_WHITE = -1;

  public static final int COLOR_BLACK = 1;

  /** Current color to move (cloned) */
  private int colorToMove = 0;

  /** Initial color to move (cloned) */
  private int initialColorToMove = 0;

  /** History of boards (cloned) */
  private ArrayList<Board> historyBoards = null;

  /** History of moves (cloned) */
  private ArrayList<Move> historyMoves = null;

  /** Move Count offset of history (cloned) */
  private int historyMoveCountOffset = 0;

  /** Hit move count offset (cloned() */
  private int historyHitMoveOffset = 0;

  @Override
  public Object clone() throws CloneNotSupportedException {
    Game result = (Game) super.clone();

    if (historyBoards != null) {
      result.historyBoards = new ArrayList<Board>();
      for (int i = 0; i < historyBoards.size(); i++) {
        result.historyBoards.add(historyBoards.get(i).clone());
      }
    }

    if (historyMoves != null) {
      result.historyMoves = new ArrayList<Move>();
      for (int i = 0; i < historyMoves.size(); i++) {
        result.historyMoves.add(historyMoves.get(i).clone());
      }
    }

    return result;
  }

  public Game(Board initialBoard, int colorToMove) {
    this.colorToMove = colorToMove;
    this.initialColorToMove = colorToMove;

    historyBoards = new ArrayList<Board>();
    historyMoves = new ArrayList<Move>();

    historyBoards.add(initialBoard);

    logger.fine("Initial chessboard:" + ChessTools.dumpBoard(getCurrentBoard()));

  }

  public Game(String fen) throws BoardException {
    StringTokenizer st = new StringTokenizer(fen, " ");

    String position = st.nextToken();

    Board initialBoard = new Board(position);

    historyBoards = new ArrayList<Board>();
    historyMoves = new ArrayList<Move>();

    String toMove = st.nextToken();
    if (toMove.equalsIgnoreCase("w")) {
      this.colorToMove = COLOR_WHITE;
      this.initialColorToMove = COLOR_WHITE;
    } else {
      this.colorToMove = COLOR_BLACK;
      this.initialColorToMove = COLOR_BLACK;
    }

    String castlingAllowed = st.nextToken();
    initialBoard.setFenCastlings(castlingAllowed);

    String epField = st.nextToken();
    initialBoard.setFenEpField(epField);
    historyBoards.add(initialBoard);

    String hitMoveCount = st.nextToken();
    int hm = new Integer(hitMoveCount).intValue();
    this.historyHitMoveOffset = hm;

    String moveCount = st.nextToken();
    int mc = new Integer(moveCount).intValue();
    if (colorToMove == COLOR_WHITE) {
      this.historyMoveCountOffset = (mc * 2) - 1;
    } else {
      this.historyMoveCountOffset = mc * 2;
    }

    logger.fine("Initial chessboard:" + ChessTools.dumpBoard(getCurrentBoard()));

  }

  public int getCurrentColorToMove() {
    return colorToMove;
  }

  public Board getCurrentBoard() {
    return historyBoards.get(historyBoards.size() - 1);
  }

  private void switchCurrentColorToMove() throws GameException {
    colorToMove = GameTools.switchColor(colorToMove);
  }

  public Move[] getCurrentLegalMoves() throws BoardException, ChessException, GameException, MoveException {
    MoveInput[] moves = Chess.getLegalMoves(getCurrentBoard(), getCurrentColorToMove());
    Move[] result = MoveTools.alterMoves(moves, getCurrentBoard(), getCurrentColorToMove());
    String moveDump = MoveTools.getMovesDump(result);

    logger.fine("Legal moves found for current position: " + moveDump);

    return result;

  }

  private void saveMove(Move move) throws GameException, BoardException {

    // Add move to history
    Board newBoard = null;
    try {
      newBoard = getCurrentBoard().clone();
    } catch (CloneNotSupportedException e) {
      throw new GameException("Clone error", e);
    }

    newBoard.applyMove(move);
    historyBoards.add(newBoard);
    historyMoves.add(move);

    switchCurrentColorToMove();

    if (logger.isLoggable(Level.FINE)) {
      logger.fine("Added move to game: " + MoveTools.getMoveDump(move));
      logger.fine("Current chess board:" + ChessTools.dumpBoard(getCurrentBoard()));
      logger.fine("Current chess board (fen):" + PGN.exportFEN(this));
    }
  }

  public void enterNextMove(MoveInput move) throws GameException, GameIllegalMoveException, BoardException, ChessException, MoveException {
    if (move.getSanInput() != null) {
      enterNextMove(move.getSanInput());
      return;
    }

    // Check move legality
    Move[] legalMoves = getCurrentLegalMoves();

    Move moveToAdd = null;

    for (int i = 0; i < legalMoves.length; i++) {
      if (legalMoves[i].getFrom().equalsIgnoreCase(move.getFrom()) && legalMoves[i].getTo().equalsIgnoreCase(move.getTo()) && legalMoves[i].getPromotion() == move.getPromotion()) {
        moveToAdd = legalMoves[i];
      }
    }

    if (moveToAdd != null) {
      saveMove(moveToAdd);
    } else {
      throw new GameIllegalMoveException("Illegal move!");
    }
  }

  public boolean isPromotionMove(MoveInput move) throws BoardException, ChessException, GameException, MoveException {
    // Check move legality
    Move[] legalMoves = getCurrentLegalMoves();

    for (int i = 0; i < legalMoves.length; i++) {
      if (legalMoves[i].getFrom().equalsIgnoreCase(move.getFrom()) && legalMoves[i].getTo().equalsIgnoreCase(move.getTo()) && legalMoves[i].isPromotion()) {
        return true;
      }
    }

    return false;
  }

  public void enterNextMove(String move) throws GameException, BoardException, ChessException, MoveException {
    Move moveToAdd = MoveTools.alterCheckSanMove(move, getCurrentLegalMoves());

    if (moveToAdd != null) {
      saveMove(moveToAdd);
    } else {
      throw new GameIllegalMoveException("Illegal move!");
    }

  }

  public void takeBackMove() throws GameException {
    if (historyMoves.size() > 0) {
      historyMoves.remove(historyMoves.size() - 1);
      historyBoards.remove(historyBoards.size() - 1);
      switchCurrentColorToMove();
    }
  }

  public Move getLastMove() {
    return this.historyMoves.get(historyMoves.size() - 1);
  }

  public GameState getCurrentGameState() throws ChessException, BoardException {
    return GameTools.getCurrentGameState(historyBoards, getCurrentBoard(), getCurrentColorToMove());
  }

  public int getInitialColorToMove() {
    return initialColorToMove;
  }

  public ArrayList<Board> getHistoryBoards() {
    return historyBoards;
  }

  public ArrayList<Move> getHistoryMoves() {
    return historyMoves;
  }

  public int getMoveCount() {
    return historyMoves.size() + historyMoveCountOffset;
  }

  public String getFEN() {
    return PGN.exportFEN(this);
  }

  public String getPlayedPGN(int limit) {
    return PGN.exportPGNMovesLine(this, limit);
  }

  public int getHistoryHitMoveOffset() {
    return historyHitMoveOffset;
  }

  public void setHistoryHitMoveOffset(int historyHitMoveOffset) {
    this.historyHitMoveOffset = historyHitMoveOffset;
  }

}
